package com.njtoyo.taxi.migration.task;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.njtoyo.taxi.migration.config.DbConfig;
import com.njtoyo.taxi.migration.entity.DbSchemaVersion;
import com.njtoyo.taxi.migration.repository.DbSchemaVersionRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author Dell
 * @Date 2019/11/20 9:17
 */
@Component
@Slf4j
@DS("secondary")
@Order
public class MigrationTask implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private DbSchemaVersionRepository dbSchemaVersionRepository;

    @Autowired
    private DbConfig dbConfig;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        if(dbConfig.getDebug()){
            log.info("-------------------debug---------------------");
            return;
        }
        showRuntimeEvn(contextRefreshedEvent);
        List<DbSchemaVersion> schemaVersions = getSchemaVersions();
        List<File> files = getSqlList(dbConfig.getMigrationLocation());
        if(CollectionUtil.isEmpty(schemaVersions)){
            log.info("schema is empty");
            executeSqlWithOrder(files);
        }else{
            log.info("schema not empty, {}",files);
            List<String> success = schemaVersions.parallelStream().filter(e -> e.getSuccess()).map(e -> e.getScript()).collect(Collectors.toList());
            List<File> collect = files.parallelStream().filter(e -> !success.contains(e.getName())).collect(Collectors.toList());
            executeSqlWithOrder(collect);
        }
    }

    public void showRuntimeEvn(ContextRefreshedEvent contextRefreshedEvent){
        ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
        Environment environment = applicationContext.getEnvironment();
        String[] profiles = environment.getActiveProfiles();
        String profile = ArrayUtil.isEmpty(profiles) ? "default" : profiles[0];
        String url = environment.getProperty("spring.datasource.url");
        log.error("------------------------------start-----------------------------------------------");
        log.info("Spring profile is {},database url : {} ", profile , url);
        log.error("-------------------------------end---------------------------------------------");
    }

    @Transactional(rollbackFor = Exception.class)
    public void executeSqlWithOrder(List<File> files){
        Map<File, List<String>> fileListMap = readSql(files);
        fileListMap.forEach((k,v) -> {
            DbSchemaVersion version = new DbSchemaVersion();
            String script = k.getName();
            String versionName = script.split("__")[0];
            String other = script.split("__")[1];
            String type = FileUtil.extName(k);
            version.setVersion(versionName);
            version.setDescription(other.substring(0,other.indexOf(".")));
            version.setType(type);
            version.setScript(script);
            version.setInstalledBy(dbConfig.getInstalledBy());
            version.setInstalledOn(new Date());
            version.setSuccess(false);
            version = dbSchemaVersionRepository.saveAndFlush(version);
            for (String sql : v){
                log.info("sql : {}",sql);
                jdbcTemplate.execute(sql);
            }
            version.setSuccess(true);
            dbSchemaVersionRepository.saveAndFlush(version);
        });
    }

    public List<DbSchemaVersion> getSchemaVersions(){
        Boolean flag;
        try {
            flag = isSchemaVersionExist();
            if(!flag){
                initSchemeVersion();
                return new ArrayList<>();
            }else{
                return dbSchemaVersionRepository.findAll();
            }
        } catch (SQLException e) {
            e.printStackTrace();
            return new ArrayList<>();
        }
    }

    public Boolean isSchemaVersionExist() throws SQLException {
        Connection connection = jdbcTemplate.getDataSource().getConnection();
        ResultSet rs = connection.
                getMetaData().getTables(connection.getCatalog(), null, dbConfig.getSchemaTableName() , new String[] { "TABLE" });
        return rs.next();
    }

    public void initSchemeVersion()  {
        List<File> files = getSqlList("db/init");
        jdbcTemplate.execute(readSqlToList(files.get(0)).get(0));
    }

    private List<File> getSqlList(String path){
        return FileUtil.loopFiles(path, temp -> temp.isFile() && FileUtil.extName(temp).equalsIgnoreCase("sql"));
    }

    private List<String> readSqlToList(File file){
        List<String> temp = new ArrayList<>();
        List<String> string = FileUtil.readUtf8Lines(file).stream().filter(e -> !e.startsWith("--")).collect(Collectors.toList());
        String sql = String.join("", string);
        if(StrUtil.count(sql,";")>1){
            String[] split = sql.split(";");
            temp.addAll(Arrays.asList(split));
        }else{
            temp.add(sql);
        }
        return temp;
    }

    private Map<File,List<String>> readSql(List<File> files){
        Map<File,List<String>> result = new TreeMap<>();
        for (File file : files) {
            List<String> temp = readSqlToList(file);
            result.put(file,temp);
        }
        return result;
    }
}
